openapi: "3.1.0"
info:
  version: "{{microfeedVersion}}"
  title: "microfeed api"
  description: |
    Allows to use microfeed as a headless cms or integrate with external services like Zapier.
    Using the microfeed api, you can programmatically create, update, delete items, and update the channel metadata.
  termsOfService: "https://github.com/microfeed/microfeed/blob/main/LICENSE"
  contact:
    name: "Listen Notes, Inc."
    url: "https://www.microfeed.org"
    email: "support@microfeed.org"
servers:
  - url: {{{baseUrl}}}
    description: API Production Server
tags:
  - name: Feed API
    description: |
      Read-only endpoints to fetch the jsonfeed and individual items.
      Anyone can use Feed API endpoints without authentication.
      In other words, these endpoints are public.
      You can disable Feed API endpoints at {{baseUrl}}/admin/settings/ -
      toggle "Visible" of "JSON" in the "Subscribe methods" section.
  - name: Admin API
    description: |
      Endpoints to do CRUD operations on the microfeed instance, e.g.,
      create a new item, update an existing item, delete an item, upload media files...
      You need to use your microfeed instance's API key to access these endpoints.
      You can find your microfeed instance's API key at the API section of {{baseUrl}}/admin/settings/.
paths:
  /json:
    get:
      tags:
        - Feed API
      summary: Fetches json for a feed
      description: |
        Fetches json data for a feed, including channel metadata and a list of items.
        The response data is in JSON feed format. For the formal JSON feed definition,
        please see https://www.jsonfeed.org/version/1.1/
        The response data can also be used as variables in the Web Feed mustache template at
        /admin/settings/code-editor/?type=themes&theme=custom#webFeed
      operationId: fetchFeedJson
      parameters:
        - name: next_cursor
          in: query
          description: |
            Offset for items, for pagination. You'll use **next_cursor** from response for this parameter.
          required: false
          schema:
            type: integer
            example: 1672707197212
        - name: sort
          in: query
          description: |
            How do you want to sort these items?
          required: false
          schema:
            type: string
            example: newest_first
            default: newest_first
            enum: [ "newest_first", "oldest_first" ]
      responses:
        200:
          description: Success response.
          links:
            paginate:
              operationId: fetchFeedJson
              parameters:
                next_cursor: $response.body#/_microfeed/items_next_cursor
                sort: $response.body#/_microfeed/items_sort_order
              description: Pagination through items.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedJson"
        404:
          description: |
            This microfeed instance's Json feed is disabled by the admin at /admin/settings/
          content:
            text/plain:
              schema:
                type: string
                description: "Not Found"
  /i/{id}/json:
    get:
      tags:
        - Feed API
      summary: Fetches json for an item
      description: |
        Fetches json feed data for an item. The items data field contains only 1 item.
        The response data is in JSON feed format. For the formal JSON feed definition,
        please see https://www.jsonfeed.org/version/1.1/
      operationId: fetchItemJson
      parameters:
        - $ref: '#/components/parameters/itemIdParam'
      responses:
        200:
          description: Success response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedJson"
        404:
          description: |
            This item does not exist, or
            this microfeed instance's Json feed is disabled by the admin at /admin/settings/
          content:
            text/plain:
              schema:
                type: string
                description: "Not Found"
  /api/channels/primary/:
    put:
      tags:
        - Admin API
      summary: Update the channel
      description: |
        Update the channel's metadata.
      operationId: apiUpdatePrimaryChannel
      parameters:
      - $ref: '#/components/parameters/apiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChannelRequestBodyJson'
      responses:
        200:
          description: Success response.
        401:
          $ref: '#/components/responses/Unauthorized'
        5XX:
          $ref: '#/components/responses/ServerError'
  /api/items/:
    post:
      tags:
        - Admin API
      summary: Creates a new item.
      description: |
        Creates a new item.
      operationId: apiCreateNewItem
      parameters:
      - $ref: '#/components/parameters/apiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ItemRequestBodyJson'
      responses:
        201:
          description: Success response.
        401:
          $ref: '#/components/responses/Unauthorized'
        5XX:
          $ref: '#/components/responses/ServerError'
  /api/items/{id}/:
    put:
      tags:
        - Admin API
      summary: Updates an item.
      description: |
        Updates an item's metadata.
      operationId: apiUpdateItemById
      parameters:
      - $ref: '#/components/parameters/apiKeyParam'
      - $ref: '#/components/parameters/itemIdParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ItemRequestBodyJson'
      responses:
        200:
          description: Success response.
        404:
          $ref: '#/components/responses/NotFound'
        401:
          $ref: '#/components/responses/Unauthorized'
        5XX:
          $ref: '#/components/responses/ServerError'
    delete:
      tags:
        - Admin API
      summary: Deletes an item.
      description: |
        Deletes an item.
        Please note that this endpoint doesn't delete the media files associated with this item (e.g., audio, image...).
      operationId: apiDeleteItemById
      parameters:
      - $ref: '#/components/parameters/apiKeyParam'
      - $ref: '#/components/parameters/itemIdParam'
      responses:
        200:
          description: Success response.
        404:
          $ref: '#/components/responses/NotFound'
        401:
          $ref: '#/components/responses/Unauthorized'
        5XX:
          $ref: '#/components/responses/ServerError'
    get:
      tags:
        - Admin API
      summary: Fetches json for an item.
      description: |
        Fetches json feed data for an item.
        This endpoint returns the same result as *GET /i/{id}/json*.
        The only difference is that *GET /i/{id}/json* doesn't require an API key and
        can be disabled via /admin/settings/.
      operationId: apiFetchItemById
      parameters:
      - $ref: '#/components/parameters/apiKeyParam'
      - $ref: '#/components/parameters/itemIdParam'
      responses:
        200:
          description: Success response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedJson"
        404:
          $ref: '#/components/responses/NotFound'
        401:
          $ref: '#/components/responses/Unauthorized'
        5XX:
          $ref: '#/components/responses/ServerError'
  /api/feed/:
    get:
      tags:
        - Admin API
      summary: Fetches json for a feed.
      description: |
        Fetches json feed data for a feed.
        This endpoint returns the same result as *GET /json*.
        The only difference is that *GET /json* doesn't require an API key and
        can be disabled via /admin/settings/.
      operationId: apiFetchFeed
      parameters:
      - $ref: '#/components/parameters/apiKeyParam'
      responses:
        200:
          description: Success response.
          links:
            paginate:
              operationId: apiFetchFeed
              parameters:
                next_cursor: $response.body#/_microfeed/items_next_cursor
                sort: $response.body#/_microfeed/items_sort_order
              description: Pagination through items.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedJson"
        401:
          $ref: '#/components/responses/Unauthorized'
        5XX:
          $ref: '#/components/responses/ServerError'
  /api/media_files/presigned_urls/:
    post:
      tags:
        - Admin API
      summary: Creates a r2 presigned url
      description: |
        Creates a presigned url for uploading a media file to R2.

        How to upload a file to R2 via microfeed API:

        1. Get a presigned url via
          ```
          curl -X POST $microfeed_url/api/media_files/presigned_urls/ \
            -H "Content-Type: application/json" \
            -H "X-MicrofeedAPI-Key: $MICROFEED_API_KEY" \
            -d '{"item_id": "$ITEM_ID", "category": "image", "full_local_file_path": "$FULL_LOCAL_FILE_PATH"}'
          ```
          You'll get a response like *{"presigned_url": $PRESIGNED_URL, "media_url": $MEDIA_URL}*

        2. Then upload a local file to R2 via the returned $PRESIGNED_URL
          ```
          curl -X PUT -T $FULL_LOCAL_FILE_PATH $PRESIGNED_URL
          ```
          Once you get a status 200 from the response, you can use the $MEDIA_URL to
          update a channel's icon or an item's image / attachment.url.

      operationId: apiGetR2PresignedUrl
      parameters:
      - $ref: '#/components/parameters/apiKeyParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PresignedUrlRequestBodyJson'
      responses:
        201:
          description: Success response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresignedUrlResponseJson'
        401:
          $ref: '#/components/responses/Unauthorized'
        5XX:
          $ref: '#/components/responses/ServerError'
components:
  parameters:
    apiKeyParam:
      name: X-MicrofeedAPI-Key
      in: header
      required: true
      style: simple
      explode: false
      schema:
        type: string
      description: "Get API Key on {{baseUrl}}/admin/settings/"
    itemIdParam:
      name: id
      in: path
      description: An item's id.
      required: true
      style: simple
      explode: false
      schema:
        type: string
      example: GOwipulXxh9
  schemas:
    FeedJson:
      type: object
      properties:
        version:
          type: string
          description: |
            The URL of the json feed version of the format the feed uses.
            See https://www.jsonfeed.org/version/1.1/
          example: "https://jsonfeed.org/version/1.1"
          required: true
        title:
          type: string
          description: |
            A channel's name.
            If this channel is a podcast, then it would be a podcast name, e.g., The Joe Rogan Experience, The Daily...
            If this is a blog, then it's the blog site's name, e.g., TechCrunch, Daring Fireball...
          example: Joe Joe and John John Podcast
          required: true
        description:
          type: string
          description: |
            A channel's description.
            If this channel is a podcast, the maximum amount of text allowed for the description is 4000 characters - this is a requirement of Apple Podcasts.
            If this is a blog, then you can write as many words as you want for the description.
          example: <p>some html description here</p>
        home_page_url:
          type: string
          description: |
            A channel's website. By default, it's the url of this website.
            But you can set it to fit your use case, e.g., your homepage on your university's website.
          example: https://my-website.com
        feed_url:
          type: string
          description: |
            The URL of the json feed, and serves as the unique identifier for the feed.
          example: https://my-website.com/json
        next_url:
          type: string
          description: |
            The URL of a feed that provides the next n items, where n is defined in /admin/settings/.
            This allows for pagination.
          example: https://my-website.com/json/?next_cursor=1672707197212&sort=newest_first
        icon:
          type: string
          description: |
            A channel's image.
            If it's a podcast, then the image must be a minimum size of 1400 x 1400 pixels and
            a maximum size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi,
            with appropriate file extensions (.jpg, .png), and in the RGB colorspace -
            this is the requirement of Apple Podcasts.
          example: https://media-cdn.my-website.com/projectname/production/images/channel-1f7e6bdb428ee91c298dfe1bc399fd23.png
        favicon:
          type: string
          description: |
            The url of an image to be used as the favicon of this site,
            which can be changed in /admin/settings/.
          example: https://cdn-my-website.com/images/favicon.jpg
        language:
          type: string
          description: |
            A channel's language.
            The primary language for the feed in the format specified in RFC 5646.
            The value is usually a 2-letter language tag from ISO 639-1,
            optionally followed by a region tag. (Examples: en or en-US.)
          example: en-us
          default: en-us
        items:
          type: array
          description: |
            The first n items in the feed.
            The sort order is determined by _microfeed.items_sort_order.
            Please follow the next_url field to see next page of items.
          items:
            $ref: "#/components/schemas/ItemJson"
        _microfeed:
          $ref: "#/components/schemas/FeedMicrofeedJson"
    ItemJson:
      type: object
      properties:
        id:
          type: string
          description: A unique id of this item.
          example: EOvEbn2lb8r
          required: true
        title:
          type: string
          description: |
            An item's title. It could be a podcast episode's title, a blog post's title,
            a custom title of a curated article...
            If it's a podcast episode, please don’t specify the episode number or season number in this tag.
            Instead, specify those details in the appropriate tags ( , ).
            Also, don’t repeat the title of your show within your episode title.
          example: A great episode with an awesome interviewee
        attachments:
          type: array
          description: |
            An item can have an associated media file, which can be
            an audio, a video, a document, an image or an external url.
            Although this is an array, there can be up to 1 attachment.
          items:
            type: object
            properties:
              url:
                type: string
                description: |
                  The url of a media file associated with this item, which can be
                  an audio, a video, a document, an image or an external url.
                example: https://media-cdn.mywebsite.org/projectname/production/media/audio-bd99c7997f0e181e3c4ad411a1160333.mp3
                required: true
              mime_type:
                type: string
                description: |
                  The type of the attachment, such as audio/mpeg.
                example: audio/mpeg
                required: true
              size_in_bytes:
                description: |
                  The file size of this attachment in bytes, if it is not an external url.
                type: integer
                example: 256789
              duration_in_seconds:
                type: integer
                description: |
                  The duration of this attachment in seconds, if it is an audio or an video.
                example: 259677
        url:
          type: string
          description: |
            An item's web link. By default, it's a web page on this microfeed site.
            But you can set it to fit your use case in the admin dashboard at /admin/.
          example: https://my-website.com/i/my-episode-EOvEbn2lb8r/
        content_html:
          type: string
          description: |
            An item's description.
            If this is a podcast episode, you'd better limit the description length to be within 4000 characters,
            or Apple Podcasts and other podcast apps/websites may truncate the text.
            If this is a blog post, then you can write as many words as you want :)
          example: <p>This is awesome</p>
        content_text:
          type: string
          description: |
            The plain text version of content_html.
          example: This is awesome
        image:
          type: string
          description: |
            A square-sized image specific to this item.
            If it's a podcast episode, the image size should be a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels,
            in JPEG or PNG format, 72 dpi, with appropriate file extensions (.jpg, .png),
            and in the RGB colorspace - this is a requirement of Apple Podcasts.
          example: https://media-cdn.my-website.com/projectname/production/images/item-1f7e6bdb428ee91c298dfe1bc399fd23.png
        banner_image:
          type: string
          description: |
            The url of an image in attachments, only when the associated media file is an image.
          example: https://media-cdn.my-website.com/projectname/production/media/image-ef5e5ec8c909ed44a800a03fe5667b50.png
        date_published:
          type: string
          description: |
            The date and time when an item was released, in RFC 3339 format.
          example: 2023-01-03T00:35:14.994Z
        _microfeed:
          $ref: "#/components/schemas/ItemMicrofeedJson"
    FeedMicrofeedJson:
      type: object
      description: |
        microfeed-specific data fields for a feed.
      properties:
        microfeed_version:
          type: string
          description: |
            The microfeed version in Semantic Versioning (x.y.z or major.minor.patch).
            Backward compatibility is maintained in a major version.
            For example, 0.2.2 should be backward compatible with 0.1.1, because both have the same major version 0;
            2.1.2 may or may not be backward compatible with 0.2.1, because they have different major versions
            (i.e., 2 vs 0).
          example: {{microfeedVersion}}
        base_url:
          type: string
          description: |
            The base url of current site. You can use multiple domain names for the same microfeed instance,
            and dynamically render pages based on this field.
          example: https://www.my-website.com
        categories:
          type: array
          description: |
            If this feed is for a podcast, then there should be podcast categories.
            All available categories are from Apple Podcasts:
            https://podcasters.apple.com/support/1691-apple-podcasts-categories
            Although you can specify more than one category, Apple Podcasts only recognizes the first category and subcategory.
            There can be up to 2 levels of categories.
          items:
            type: object
            properties:
              name:
                type: string
                description: The name of a level-1 category.
                example: Arts
              categories:
                type: array
                items:
                  type: object
                  properties:
                    name:
                      type: string
                      description: The name of a level-2 category.
                      example: Books
        subscribe_methods:
          type: array
          description: |
            All available subscription methods of this feed.
            For example, you can list all podcast apps that people can use to subscribe to this feed.
          items:
            type: object
            properties:
              name:
                type: string
                description: A human-readable name of this subscription method.
                example: RSS
              type:
                type: string
                description: The type of this subscription method.
                enum:
                  - rss
                  - json
                  - apple podcasts
                  - spotify
                  - google podcasts
                  - amazon music
                  - overcast
                  - pocket casts
                  - castro
                  - listen notes
                  - custom
              url:
                type: string
                description: The url of this subscription method.
                example: https://podcasts.apple.com/us/podcast/id1200361736
              image:
                type: string
                description: The url of this subscription method's icon.
                example: https://my-website.com/assets/brands/subscribe/rss.png
        description_text:
          type: string
          description: The plain text version of the description field of this feed / channel.
          example: This is a great podcast.
        "itunes:type":
          type: string,
          description: |
            The type of a podcast on Apple Podcasts.
            Learn more: https://help.apple.com/itc/podcasts_connect/#/itcb54353390
          enum:
            - episodic
            - serial
          default: episodic
        items_sort_order:
          type: string,
          description: |
            The sort order of items.
          enum:
            - newest_first
            - oldest_first
          default: newest_first
        items_next_cursor:
          type: string
          description: |
            This data field is for pagination through items.
            You can pass a next_cursor=[items_next_cursor] query string to feed_url to fetch next page of items.
          example: 1672705853661
        "itunes:block":
          type: boolean
          description: |
            Apple Podcasts-specific data field.
            The podcast show or hide status in Apple Podcasts.
            If you want your show removed from the Apple directory, it is true.
          default: false
        "itunes:complete":
          type: boolean
          description: |
            Apple Podcasts-specific data field.
            The podcast update status.
            If you will never publish another episode to your show, select 'yes'.
            Specifying the itunes:complete tag with a 'yes' value indicates that a podcast is complete and you will not post any more episodes in the future.
            Specifying any value other than 'yes' has no effect.
          default: false
        "itunes:new-feed-url":
          description: |
            Apple Podcasts-specific data field.
            The new podcast RSS Feed URL.
            If you change the URL of your podcast feed, you should use this tag in your new feed.
            Use the itunes:new-feed-url tag to manually change the URL where your podcast is located.
            You should maintain your old feed until you have migrated your existing subscribers. Learn how to update your podcast RSS feed URL.
            Note: The itunes:new-feed-url tag reports new feed URLs to Apple Podcasts and isn’t displayed in Apple Podcasts.
          type: string
        "itunes:email":
          type: string
          description: |
            Apple Podcasts-specific data field.
            The podcast owner's contact email. It'll be public in the rss feed.
            Many podcast platforms require this email to validate your podcast ownership.
          example: myemail@mycompany.com
        "itunes:explicit":
          type: boolean
          description: |
            The podcast parental advisory information.
            If you specify true, indicating the presence of explicit content, Apple Podcasts displays an Explicit parental advisory graphic for your podcast.
            Podcasts containing explicit material aren’t available in some Apple Podcasts territories.
            If you specify false, indicating that your podcast doesn’t contain explicit language or adult content, Apple Podcasts displays a Clean parental advisory graphic for your podcast.
          default: false
        "itunes:title":
          type: string
          description: |
            The show title specific for Apple Podcasts.
            itunes:title is a string containing a clear concise name of your show on Apple Podcasts.
          example: An alternative title on Apple Podcasts only
    ItemMicrofeedJson:
      type: object
      description: microfeed-specific data fields for an item.
      properties:
        status:
          type: string
          description: The status of this item.
          enum:
            - published
            - unlisted
            - unpublished
        is_audio:
          type: boolean
          description: Is the media file associated with this item an audio?
          default: false
        is_document:
          type: boolean
          description: Is the media file associated with this item a document?
          default: false
        is_external_url:
          type: boolean
          description: Is the media file associated with this item an external url?
          default: false
        is_video:
          type: boolean
          description: Is the media file associated with this item a video?
          default: false
        is_image:
          type: boolean
          description: Is the media file associated with this item an image?
          default: false
        web_url:
          type: string
          description: The url of this item's web page
          example: https://my-website.com/i/some-slug-EOvEbn2lb8r/
        json_url:
          type: string
          description: The url of this item's json feed
          example: https://my-website.com/i/some-slug-EOvEbn2lb8r/
        rss_url:
          type: string
          description: The url of this item's rss feed
          example: https://my-website.com/i/some-slug-EOvEbn2lb8r/
        guid:
          type: string
          description: |
            An item's guid. By default, it's the id of this item. But you can set it to fit your use case.
            It is very important that each episode have a unique GUID and that it never changes once it's set,
            even if an episode’s metadata, like title or enclosure URL, do change.
            Failing to comply with these guidelines may result in duplicate episodes being shown to listeners,
            inaccurate data in Analytics, and can cause issues with your podcasts’s listing and
            chart placement in Apple Podcasts and other podcast apps/websites.
          example: EOvEbn2lb8r
        date_published_short:
          type: string
          description: |
            This item's published date in human friendly format, using the website visitor's timezone.
          example: Mon Jan 02 2023
        date_published_ms:
          type: integer
          description: The item's published date in milliseconds.
          example: 1672707197212
        "itunes:title":
          type: string
          description: |
            An episode title specific for Apple Podcasts.itunes:title is a string containing a clear concise name of your episode on Apple Podcasts.
            Don’t specify the episode number or season number in this tag. Instead, specify those details in the appropriate tags ( itunes:episode, itunes:season). Also, don’t repeat the title of your show within your episode title.
            Separating episode and season number from the title makes it possible for Apple to easily index and order content from all shows.
          example: An alternative title of this episode on Apple Podcasts
        "itunes:block":
          type: boolean
          description: |
            The episode show or hide status on Apple Podcasts.
            If you want an episode removed from the Apple directory, set it to true.
            Specifying the itunes:block tag with a true value prevents that episode from appearing in Apple Podcasts.
            For example, you might want to block a specific episode if you know that its content would otherwise cause the entire podcast to be removed from Apple Podcasts.
            Specifying any value other than true has no effect.
        "itunes:episodeType":
          type: string
          description: |
            The episode type in Apple Podcasts. Learn more:
            https://help.apple.com/itc/podcasts_connect/#/itcb54353390
          enum:
            - full
            - trailer
            - bonus
          default: full
        "itunes:season":
          type: integer
          description: |
            The episode season number in Apple Podcasts.
            If an episode is within a season use this tag.
            Where season is a non-zero integer (1, 2, 3, etc.) representing your season number.
            To allow the season feature for shows containing a single season, if only one season exists in the RSS feed, Apple Podcasts doesn’t display a season number. When you add a second season to the RSS feed, Apple Podcasts displays the season numbers.
        "itunes:episode":
          type: integer
          description: |
            An episode number in Apple Podcasts.
        "itunes:explicit":
          type: boolean
          description: |
            The episode parental advisory information.
            If you specify true, indicating the presence of explicit content, Apple Podcasts displays an Explicit parental advisory graphic for your episode. Episodes containing explicit material aren’t available in some Apple Podcasts territories.
            If you specify false, indicating that the episode does not contain explicit language or adult content, Apple Podcasts displays a Clean parental advisory graphic for your episode.
          default: false
    ChannelRequestBodyJson:
      type: object
      properties:
        title:
          type: string
          description: |
            The channel's title.
            If this channel is a podcast, then it would be a podcast name, e.g., The Joe Rogan Experience, The Daily...
            If this is a blog, then it's the blog site's name, e.g., TechCrunch, Daring Fireball...
          example: The Daily Podcast
        homepage_url:
          type: string
          description: |
            A channel's website.
          example: https://my-website.com
        description:
          type: string
          description: |
            A channel's description in html.
            If this channel is a podcast, the maximum amount of text allowed for the description is 4000 characters -
            this is a requirement of Apple Podcasts.
            If this is a blog, then you can write as many words as you want for the description.
          example:
            <b>hello</b> This ia a description!
        icon:
          type: string
          description:
            A channel's cover image url.
            If it's a podcast, then the image must be a minimum size of 1400 x 1400 pixels and
            a maximum size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi,
            with appropriate file extensions (.jpg, .png), and in the RGB colorspace -
            this is the requirement of Apple Podcasts.
            The image should be uploaded to the R2 bucket used by this microfeed instance.
            You can use *POST /api/v1/media_files/presigned_urls/* to upload the image to the R2 bucket.
            Then you can use the returned *media_url* as the icon.
          example:
            https://media-cdn.my-website.com/projectname/production/images/channel-1f7e6bdb428ee91c298dfe1bc399fd23.png
        authors:
          type: array
          description: |
            A channel's authors / publishers.
            For now, we only support up to 1 author / publisher.
          items:
            type: object
            properties:
              name:
                type: string
                description: |
                  A channel's author / publisher.
                  If this channel is a podcast, then it would be the publisher's name,
                  e.g., Gimlet Media, New York Times, Joe Rogan...
                example: John Doe
        language:
          type: string
          description: |
            A channel's language.
            The primary language for the feed in the format specified in RFC 5646.
            The value is usually a 2-letter language tag from ISO 639-1, optionally followed by a region tag.
          example: en-us
        expired:
          type: boolean
          description: |
            Usually used for a podcast.
            If you will never publish a new item in the future, set it to true.
          example: false
        _microfeed:
          type: object
          description: |
            Data fields that are not in the jsonfeed.org standard.
          properties:
            itunes:explicit:
              type: boolean
              description: |
                The podcast parental advisory information.
                If you specify true, indicating the presence of explicit content, Apple Podcasts displays an Explicit parental advisory graphic for your podcast.
                Podcasts containing explicit material aren’t available in some Apple Podcasts territories.
                If you specify false, indicating that your podcast doesn’t contain explicit language or adult content, Apple Podcasts displays a Clean parental advisory graphic for your podcast.
              example: false
            itunes:title:
              type: string
              description: |
                The show title specific for Apple Podcasts.
                itunes:title is a string containing a clear concise name of your show on Apple Podcasts.
              example: Another title
            itunes:block:
              type: boolean
              description: |
                The podcast show or hide status in Apple Podcasts.
                If you want your show removed from the Apple directory, set it to true.
              example: false
            itunes:type:
              type: string
              description: |
                The type of show.
                If your show is Serial you must use this tag.
                Its values can be one of the following:
                1) Episodic (default). Specify episodic when episodes are intended to be consumed without any specific order. Apple Podcasts will present newest episodes first and display the publish date (required) of each episode. If organized into seasons, the newest season will be presented first - otherwise, episodes will be grouped by year published, newest first.
                For new subscribers, Apple Podcasts adds the newest, most recent episode in their Library.
                2) Serial. Specify serial when episodes are intended to be consumed in sequential order. Apple Podcasts will present the oldest episodes first and display the episode numbers (required) of each episode. If organized into seasons, the newest season will be presented first and itunes:episode numbers must be given for each episode.
              enum:
                - serial
                - episodic
              example: episodic
            copyright:
              type: string
              description: |
                The show copyright details.
                If your show is copyrighted you should use this tag.
              example: Copyright 1995-2019 John John Appleseed
            itunes:email:
              type: string
              description: |
                The podcast owner's contact email.
                It'll be public in the rss feed. Many podcast platforms require this email to validate your podcast ownership.
              example: abc@example.com
    ItemRequestBodyJson:
      type: object
      properties:
        title:
          type: string
          description: |
            An item's title. It could be a podcast episode's title, a blog post's title, a custom title of a curated article...
            If it's a podcast episode, please don’t specify the episode number or season number in this tag. Instead, specify those details in the appropriate tags ( , ).
            Also, don’t repeat the title of your show within your episode title.
          example: Episode 1 - Introduction
        status:
          type: string
          description: |
            An item's status is either published, unlisted, or unpublished.
            1. published: listed on the web/rss/json feed, and visible via the direct web link.
            2. unpublished: not listed on the web/rss/json feed, and not visible via the direct web link. An admin can still find it and edit on the See all items page.
            3. unlisted: not listed on the web/rss/json feed, but visible via the direct web link.
          enum:
            - published
            - unpublished
            - unlisted
        attachment:
          type: object
          description: |
            An item can have a main media file.
            For example, if it's a podcast episode, then a media file should be an audio;
            if it's a self-hosted photo album, then a media file should be a high-definition image;
            if it's a content curation site, then a media file could be an external url (e.g., an article url from New York Times).
            The media file / attachment should be uploaded to the R2 bucket used by this microfeed instance.
            You can use *POST /api/v1/media_files/presigned_urls/* to upload to the R2 bucket.
            Then you can use the returned *media_url* for *url".
          properties:
            category:
              type: string
              description: |
                The category of the attachment.
              enum:
                - audio
                - video
                - image
                - document
                - external_url
              example: audio
            url:
              type: string
              description: |
                The url of the attachment.
                If it's an audio, video, image, or document, then it should be the url of the media file uploaded to
                the R2 bucket associated with this microfeed instance.
                You can use *POST /api/v1/media_files/presigned_urls/* to upload to the R2 bucket.
                Then you can use the returned *media_url* for *url*.
                If it's an external_url, then it should be the url of the external resource.
              example: https://media-cdn.my-website.com/projectname/production/media/audio-ef5e5ec8c909ed44a800a03fe5667b50.mp3
            mime_type:
              type: string
              description: |
                The mime type of the attachment.
              example: image/png
            size_in_bytes:
              type: integer
              description: |
                The attachment's size in bytes.
              example: 4096
            duration_in_seconds:
              description: |
                If it's an audio or video, then it should be the duration of the media file in seconds.
              type: integer
              example: 602
        url:
          type: string
          description: |
            An item's web link. By default, it's a web page on your microfeed. But you can set it to fit your use case.
          example: https://example.com/episode1
        content_html:
          type: string
          description: |
            An item's description in html.
            If this is a podcast episode, you'd better limit the description length to be within 4000 characters, or Apple Podcasts and other podcast apps/websites may truncate the text.
            If this is a blog post, then you can write as many words as you want :)
          example: <b>some text</b>
        image:
          type: string
          description: |
            A square-sized image specific to this item.
            If it's a podcast episode, the image size should be a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi, with appropriate file extensions (.jpg, .png), and in the RGB colorspace - this is a requirement of Apple Podcasts.
            The image should be uploaded to the R2 bucket used by this microfeed instance.
            You can use *POST /api/v1/media_files/presigned_urls/* to upload the image to the R2 bucket.
            Then you can use the returned *media_url* as the image.
          example: https://media-cdn.my-website.com/projectname/production/images/item-1f7e6bdb428ee91c298dfe1bc399fd23.png
        date_published_ms:
          type: integer
          description: |
            The Unix epoch when an item was released, in milliseconds.
          example: 1669782691867
        _microfeed:
          type: object
          description: |
            Data fields that are not in the jsonfeed.org standard.
          properties:
            itunes:title:
              description: |
                An episode title specific for Apple Podcasts.
                itunes:title is a string containing a clear concise name of your episode on Apple Podcasts.
                Don’t specify the episode number or season number in this tag. Instead, specify those details in the appropriate tags (itunes:episode, itunes:season).
                Also, don’t repeat the title of your show within your episode title.
                Separating episode and season number from the title makes it possible for Apple to easily index and order content from all shows.
              type: string
              example: Another episode title
            itunes:block:
              type: boolean
              description: |
                The episode show or hide status on Apple Podcasts.
                If you want an episode removed from the Apple directory, specifcy true.
              example: false
            itunes:episodeType:
              type: string
              enum:
                - full
                - trailer
                - bonus
              description: |
                The episode type in Apple Podcasts.
                If an episode is a trailer or bonus content, use this tag.
                Where the episodeType value can be one of the following:
                1. full (default). Specify full when you are submitting the complete content of your show.
                2. trailer. Specify trailer when you are submitting a short, promotional piece of content that represents a preview of your current show.
                3. bonus. Specify bonus when you are submitting extra content for your show (for example, behind the scenes information or interviews with the cast) or cross-promotional content for another show.
              example: full
            itunes:season:
              type: integer
              description: |
                The episode season number in Apple Podcasts.
                If an episode is within a season use this tag.
                Where season is a non-zero integer (1, 2, 3, etc.) representing your season number.
                To allow the season feature for shows containing a single season, if only one season exists in the RSS feed, Apple Podcasts doesn’t display a season number. When you add a second season to the RSS feed, Apple Podcasts displays the season numbers.
              example: 1
            itunes:episode:
              type: integer
              description: |
                An episode number in Apple Podcasts.
                If all your episodes have numbers and you would like them to be ordered based on them, use this tag for each one.
                Episode numbers are optional for itunes:type episodic shows, but are mandatory for serial shows.
                Where episode is a non-zero integer (1, 2, 3, etc.) representing your episode number.
                If you are using your RSS feed to distribute a free version of an episode that is already available to Apple Podcasts paid subscribers, make sure the episode numbers are the same so you don’t have duplicate episodes appear on your show page. Learn more about how to set up your show for a subscription.
              example: 2
            itunes:explicit:
              type: boolean
              description: |
                The episode parental advisory information.
                If you specify true, indicating the presence of explicit content, Apple Podcasts displays an Explicit parental advisory graphic for your episode. Episodes containing explicit material aren’t available in some Apple Podcasts territories.
              example: false

    PresignedUrlRequestBodyJson:
      type: object
      properties:
        item_id:
          type: string
          description: |
            The id of the item that the media file belongs to.
            If the media file is a cover image of a channel (icon) or an item (image),
            then you don't need to specify the item_id.
          example: 3a0ydRs15gZ
        category:
          type: string
          description: |
            The category of the media file.
          enum:
            - image
            - audio
            - video
            - document
        full_local_file_path:
          type: string
          description: |
            The full local file path of the media file.
          example: /tmp/episode1.mp3
      required:
        - category
        - full_local_file_path
    PresignedUrlResponseJson:
      type: object
      properties:
        presigned_url:
          type: string
          description: |
            The presigned url for uploading the file to the storage.
            The url is valid for 60 minutes.
            To actually upload a media file, you need to send a PUT request to the presigned url.
            Example: *curl -X PUT -T $FULL_LOCAL_FILE_PATH $PRESIGNED_URL*
        media_url:
          type: string
          description: |
            The full url of the future uploaded file.
            Please note that the url is not valid until the file is uploaded via the presigned url.
            You can use *media_url* to update a channel's *icon*, or an item's *image* / *attachment.url*.
  responses:
    Unauthorized:
      description: Invalid api key.
    ServerError:
      description: Unexpected server errors.
    NotFound:
      description: Endpoint not exist, or item not exist.
